/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package TablaSimbolos;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *
 * @author Diego
 */
public class TabladeSimbolos {

    //HashMap<String, entradaClase> tablaClase = new HashMap(); //tabla de clases
    LinkedHashMap<String, entradaClase> tablaClase = new LinkedHashMap();
    String mensajeError;
    String claseActual;
    String metodoActual;
    ArrayList<parametro> listaTemporalParametros = new ArrayList();
    Boolean isAssignable = false;
    Boolean seConsultoPorConstructor = false;
    int lineaActual;

    public String getMetodoActual() {
        return metodoActual;
    }

    public void setMetodoActual(String metodoActual) {
        this.metodoActual = metodoActual;
    }

    public int getLineaActual() {
        return lineaActual;
    }

    public void setLineaActual(int lineaActual) {
        this.lineaActual = lineaActual;
    }

    /**
     * Limpia la lista de parametros temporales.
     */
    public void limpiarListaParametrosTemporales() {
        this.listaTemporalParametros.clear();
    }

    /**
     * agrega un parametro a la lista de parametros temporales.
     * @param p es el parametro que se quiere agregar a la lista de parametros
     * temporales.
     */
    public void agregarParametrosTemporales(parametro p) {
        this.listaTemporalParametros.add(p);
    }

    /**
     * Este metodo es solo para verificar que los parametros han sido cargados
     * correctamente en la lista de parametros temporales
     */
    public void mostrarParametrosTemp() {
        int indice;
        indice = this.listaTemporalParametros.size();
        for (int i = 0; i < indice; i++) {
            this.listaTemporalParametros.get(i).mostrarParametro();
        }
    }

    public void cargarParametros() {
        String p = "parametro_";
        String t = "int";

        for (int i = 0; i < 5; i++) {
            this.listaTemporalParametros.add(new parametro(p + i, t));
        }
        this.listaTemporalParametros.add(new parametro("param6", "Z"));
    }

    public void cargarParametros2() {
        String p = "parametro_";
        String t = "int";
        this.listaTemporalParametros.clear();
        for (int i = 0; i < 5; i++) {
            this.listaTemporalParametros.add(new parametro(p + i, t));
        }
        this.listaTemporalParametros.add(new parametro("param6", "Z"));
    }

    /**
     * perteneceTS dado el nombre de una clase retorna verdadero si el nombre 
     * pertence a la tabla de Simbolos donde hay una entrada de clase con ese 
     * nombre.
     * @param nameClase en la llave a buscar. Si el nombre esta en la tabla de
     * simbolos retorna true caso contrario retorna false.
     */
    public boolean perteneceTS(String nameClase) {
        if (this.tablaClase.get(nameClase) != null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * darClase dado el nombre de una clase retorna la entrada a esa clase
     * @param key dada una llave de tipo String busca en la tabla de Simbolos
     * si dicha clave esta presente.
     * @return entradaClase si la llave es encontrada devuelve una entrada a esa 
     * clase con nombre key.
     */
    public entradaClase darClase(String key) {
        if (perteneceTS(key)) {
            return this.tablaClase.get(key);
        } else {
            return null;
        }
    }

    /**
     * Aagrega una entrada de clase a la tabla de simbolos con nombre entryClass.
     * Esto es cuando se esta procesando el classDef. Solo se utiliza para 
     * los identidicadores que vienen despues de un classDef.    
     * Si ya esta en la tabla de simbolos solo se setea el atributo que 
     * indica que esta declarada.
     * @param entryClass el nombre de la entrada de Clase que se quiere agregar
     * a la tabla de clases manipulada por la tabla se simbolos.     
     * @throws errorTS Arroja un error si el nombre la clase que se intenta 
     * agragar ya esta en la tabla de simbolos. 
     * @see insertarClaseTS. 
     */
    public void agregarClaseDef(String entryClass) throws Exception {
        entradaClase ec, padre;
        String m;
        if (!this.perteneceTS(entryClass)) {
            ec = new entradaClase(entryClass);
            ec.setearDeclarada();
            //padre = darClase("Object");
            //ec.setPadre(padre);
            this.tablaClase.put(ec.darNombre(), ec);
            //agregarVariablesAncestros();
        } else {
            if (!this.darClase(entryClass).estaDeclarada()) {
                this.darClase(entryClass).setearDeclarada();
            } else {
                m = "Clase con nombre duplicado. La clase " + entryClass + " ya fue declarada" + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            }
        }
    }

    /**
     * Dado el nombre de una clase y el nombre del metodo busca si hay algun parametro
     * para ese metodo con nombre igual a la de la variable. De ser asi retorna el
     * nombre tipo del parametro. Si no se encuentra como parametro se devolvera en
     * caso de existir el tipo de una variable de instancia.
     */
    public String buscarTipoDeVar(String nc, String nm, String nv) throws Exception {
        String tipotmp;
        String m;
        if (this.darClase(nc).darMetodo(nm).perteneceTP(nv)) {
            tipotmp = this.darClase(nc).darMetodo(nm).darParametro(nv).toStringTipoP();
            return tipotmp;
        } else //no esta en la tabla de parameetros de ese metodo, busco si es de la clase
        {
            if (this.darClase(nc).perteneceTV(nv)) {
                this.darClase(nc).setLineaError(lineaActual);
                tipotmp = this.darClase(nc).darVariable(nv).toStringTipoV();
                return tipotmp;
            } else {
                m = "El tipo de la variable " + nv + " no pudo ser encontrado" + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            }
        }
    }

    /**
     * agregarClaseTS agrega una entrada de clase con nombre entryClass a la 
     * tabla de simbolos. Este metodo se usa cuando se procesa los body
     * del classDef. Si hay un metodo que tiene una variable de tipo entradaClase
     * no declarado aun entonces lo agrega a la tabla de clases pero lo deja 
     * como pendiente de declaracion. En caso de existir no hace nada.
     * Ej
     * classDef A{
     * B x();
     * Donde B aun no fue declarado pero se debe insertar en la tabla de clases.
     * @param entryClass es el nombre de la entrada de clase a ser agregada en 
     * la tabla de simbolos.
     */
    public void agregarClaseTS(String entryClass) {
        entradaClase ec;
        if (!this.perteneceTS(entryClass)) {
            ec = new entradaClase(entryClass);
            this.tablaClase.put(ec.darNombre(), ec);
        }
    }

    /**
     * Define la relacion de herencia entre los nombres de las clases 
     * nameClaseActual y nameClasePadre. Donde nameClaseActual hereda de
     * nameClasePadre.
     * @param nameClaseActual es la clase que va a heredar
     * @param nameClasePadre es la superclase.
     * @throws Exception Arroja un error en caso de que se quiera asignar como
     * padre a una clase no declarada.
     */
    public void agregarHerencia(String nameClaseActual, String nameClasePadre) throws Exception {
        entradaClase entradaActual, entradaPadre;
        String m;
        entradaActual = this.darClase(nameClaseActual);
        entradaPadre = this.darClase(nameClasePadre);
        if (entradaActual == null) {            
            m = "La clase " + nameClaseActual + " no se encuentra declarada aun." + ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(m, lineaActual);
            throw new errorTS(mensajeError);
        } else {
            if (entradaPadre == null) // no esta declarado el padre en la TS
            {               
                m = "Se intenta asignar como padre de " + nameClaseActual + " a una clase no declarada " + nameClasePadre+ ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            } else {
                if (entradaActual.esAncestro(entradaPadre)) {                    
                    m = "Se detecto herencia circular entre la clase " + nameClaseActual + " y la clase " + nameClasePadre+ ". Linea: " + getLineaActual();
                    mensajeError = errorTS.construirErrorTS(m, lineaActual);
                    throw new errorTS(mensajeError);
                } else {
                    if (entradaPadre.esTipoPrimitivo())//nunca deberia llegar aca x el sintactico
                    {
                        m = "La clase " + nameClaseActual + " intenta heredar del tipo primitivo " + nameClasePadre+ ". Linea: " + getLineaActual();
                        mensajeError = errorTS.construirErrorTS(m, lineaActual);
                        throw new errorTS(mensajeError);
                    } else {
                        if (nameClaseActual.equals(nameClasePadre)) {
                            m = "La clase " + nameClaseActual + " intenta heredar de si misma "+ ". Linea: " + getLineaActual();
                            mensajeError = errorTS.construirErrorTS(m, lineaActual);
                            throw new errorTS(mensajeError);
                        } else {
                            entradaActual.setLineaError(lineaActual);
                            entradaActual.setPadre(entradaPadre);
                        }

                    }
                }
            }
        }
    }

   
    /**
     * Controla que las clases que estan en la tablas de simbolos
     * esten todas declaradas. En caso de estar todas declaradas 
     * retorna verdadero caso contrario falso.
     * @return boolean verdadero si todas las clases en la tabla de simbolos
     * han sido declaradas, falso en caso contrario.
     * @throws Ocurre un error cuando se detecta que una clase no fue declarada.
     */
    public boolean verificarDeclarados() throws Exception {
        boolean seguir = true;
        boolean todosdeclaradosok = true;
        Iterator iter = tablaClase.entrySet().iterator();
        Map.Entry e;
        entradaClase entryClass;
        while ((iter.hasNext()) && seguir) {
            e = (Map.Entry) iter.next();
            entryClass = tablaClase.get((String) e.getKey());
            if (!entryClass.estaDeclarada()) {
                seguir = false;
                todosdeclaradosok = false;
                mensajeError = "La clase " + entryClass.darNombre() + " no fue declarada (classDef)." + ". Linea: " + getLineaActual();
                throw new errorTS(mensajeError);
            }
        }
        return todosdeclaradosok;
    }

    /**
     * Controla que las clases que estan en la tablas de simbolos
     * esten todas definidas. En caso de estar todas definidas 
     * retorna verdadero caso contrario falso.
     * @return boolean verdadero si todas las clases en la tabla de simbolos
     * han sido declaradas, falso en caso contrario.
     * @throws Ocurre un error cuando se detecta que una clase no fue definida.
     */
    public boolean verificarDefinidos() throws Exception {
        boolean seguir = true;
        boolean todosdefinidosok = true;
        Iterator iter = tablaClase.entrySet().iterator();
        Map.Entry e;
        entradaClase entryClass;
        while ((iter.hasNext()) && seguir) {
            e = (Map.Entry) iter.next();
            entryClass = tablaClase.get((String) e.getKey());
            if (!entryClass.estaDefinida()) {
                seguir = false;
                todosdefinidosok = false;
                mensajeError = "La clase " + entryClass.darNombre() + " no fue definida (class)."
                + ". Linea: " + getLineaActual();
                throw new errorTS(mensajeError);
            }
        }
        return todosdefinidosok;
    }

    public void verificarMetodosDefinidos() throws errorTS, Exception {
        Iterator iter = tablaClase.entrySet().iterator();
        Iterator iterMetodo;
        Map.Entry mC;
        Map.Entry mM;
        entradaClase entryClass;
        entradaMetodo entryMethod;        
        while (iter.hasNext()) {
            mC = (Map.Entry) iter.next();
            entryClass = tablaClase.get((String) mC.getKey());
            iterMetodo = entryClass.getTablaMetodo().entrySet().iterator();
            while (iterMetodo.hasNext()) {
                mM = (Map.Entry) iterMetodo.next();
                entryMethod = entryClass.darMetodo((String) mM.getKey());
                if (!entryMethod.isDefinido()) {
                    if ((entryMethod.esConstructorDeClase() && entryClass.isConstructordefault())) {
                    } else {
                        throw new errorTS("El metodo " + entryMethod.darNombre() + " no esta definido en la clase " + entryClass.darNombre()+ ". Linea: " + getLineaActual());
                    }

                }
            }
        }
    }

    /**
     * Agrega a la tabla de simbolos las clases predefinidas Object y System
     * @throws en caso de intentar cargar nuevamente las clases predefinidas
     * arroja un error indicando este suceso.
     */
    private void agregarPredefinidas() throws Exception {
        entradaClase e = new entradaClase("Object");
        e.setearDeclarada();
        e.setearDefinida();
        this.tablaClase.put(e.darNombre(), e);
        //Pendiente volver a cargar
        e = new entradaClase("System");
        e.setearDeclarada();
        e.setearDefinida();
        this.tablaClase.put(e.darNombre(), e);
        this.cargarMetodosSystem();
        setearMetodosSystemDefinidos();
    }

    /**
     * Carga los metodos que tiene la clase System.
     * Los metodos son: read, printB, printC, printI, printS
     * println, printBln, printCln, printIln, printSln.
     */
    public void cargarMetodosSystem() throws Exception {
        this.limpiarListaParametrosTemporales();
        this.agregarMetodo("System", "int", "read", this.listaTemporalParametros);
        this.agregarParametrosTemporales(new parametro("b", "boolean"));
        this.agregarMetodo("System", "void", "printB", this.listaTemporalParametros);
        this.agregarMetodo("System", "void", "printBln", this.listaTemporalParametros);
        this.limpiarListaParametrosTemporales();
        this.agregarParametrosTemporales(new parametro("c", "char"));
        this.agregarMetodo("System", "void", "printC", this.listaTemporalParametros);
        this.agregarMetodo("System", "void", "printCln", this.listaTemporalParametros);
        this.limpiarListaParametrosTemporales();
        this.agregarParametrosTemporales(new parametro("i", "int"));
        this.agregarMetodo("System", "void", "printI", this.listaTemporalParametros);
        this.agregarMetodo("System", "void", "printIln", this.listaTemporalParametros);
        this.limpiarListaParametrosTemporales();
        this.agregarParametrosTemporales(new parametro("s", "String"));
        this.agregarMetodo("System", "void", "printS", this.listaTemporalParametros);
        this.agregarMetodo("System", "void", "printSln", this.listaTemporalParametros);
        this.limpiarListaParametrosTemporales();
        this.agregarMetodo("System", "void", "println", this.listaTemporalParametros);
    }

    /**
     * Setea todos los metodos de la clase System como definido.
     * Se hace para mantener la consistencia en la tabla de simbolos 
     * a pesar que la clase System ya fue seteada como definida.
     */
    public void setearMetodosSystemDefinidos() throws Exception {
        //Si bien la clase System ya fue seteada como definida 
        //Los metodos se setean para mantener la consistencia.
        for (Map.Entry entry : this.darClase("System").tablaMetodo.entrySet()) {   //Esta recorre todas las clases.
            this.darClase("System").darMetodo((String) entry.getKey()).setearDefinido();
            this.setearOffsetParametrosSystem(this.darClase("System").darMetodo((String) entry.getKey()));
        }
    }

    public void setearOffsetParametrosSystem(entradaMetodo em) {
        int offsettemp = 1;

        for (Map.Entry entry : em.tablaParametros.entrySet()) {   //Esta recorre todas las clases.
            em.darParametro((String) entry.getKey()).setearOffsetParametro(offsettemp);
            offsettemp = offsettemp + 1;
        }
    }

    /**
     * Crea instancias de clases que representaran los tipos primitivos y la 
     * palabra reservada void usada por ejemplo para los retornos de los metodos.     * 
     */
    /**
     * Agrega a su tabla de variables las variables de sus ancestros. Esto se hace 
     * para resolver el tamaño del CIR de manera consistente.
     */
    public void agregarVariablesAncestros() {
        entradaClase ectmp, ecact;
        for (Map.Entry entry : tablaClase.entrySet()) {
            //Esta recorre todas las clases.
            //this.mostrarTodaLaClase((String)entry.getKey());
            ectmp = this.darClase((String) entry.getKey());
            ecact = ectmp.darPadre();
            while (ecact != null) {
                ectmp.tablaVariable.putAll(ecact.tablaVariable);
                ecact = ecact.darPadre();
            }
        }
    }
    //agrega los metodos del padre a la clasea actual

    public void agregarMetodosPadre() throws Exception {
        entradaClase eC = this.darClase(claseActual);
        eC.agregarMetodosAncestros();
    }
    //agrega las variables del padre a la clasea actual

    public void agregarVariablesPadres() throws Exception {
        entradaClase eC = this.darClase(claseActual);
        eC.agregarVariablesAncestros();
    }

    private void cargarPrimitivosyVoid() {
        entradaClase aux;
        aux = new entradaClase("int");
        aux.setearPrimitivo();
        aux.setearDeclarada();
        aux.setearDefinida();
        this.tablaClase.put(aux.darNombre(), aux);
        aux = new entradaClase("char");
        aux.setearPrimitivo();
        aux.setearDeclarada();
        aux.setearDefinida();
        this.tablaClase.put(aux.darNombre(), aux);
        aux = new entradaClase("boolean");
        aux.setearPrimitivo();
        aux.setearDeclarada();
        aux.setearDefinida();
        this.tablaClase.put(aux.darNombre(), aux);
        aux = new entradaClase("String");
        aux.setearPrimitivo();
        aux.setearDeclarada();
        aux.setearDefinida();
        this.tablaClase.put(aux.darNombre(), aux);
        aux = new entradaClase("void");
        aux.setearPrimitivo();
        aux.setearDeclarada();
        aux.setearDefinida();
        this.tablaClase.put(aux.darNombre(), aux);
    }

    /**
     * Verifica que la clase Main este presente en la tabla de simbolos.
     * @return Devuelve true en caso de que la clase Main este declarada.
     * Luego se verificara si la misma esta definida.
     * @throws Exception Arroja un error en el caso de que la clase Main
     * no este declarada.
     */
    public boolean estaMain() throws Exception {
        if (!(this.perteneceTS("Main"))) {
            mensajeError = "La clase Main no esta declarada.";
            throw new errorTS(mensajeError);
        } else {
            return perteneceTS("Main");
        }
    }

    /**
     * Inicializa la tabla de simbolos agregando los tipos primitivos, void y
     * las clases predefinidas como System y Object.     
     * @see cargarPrimitivosyVoid
     * @see agregarPredefinidas
     */
    public void inicializarTS() throws Exception {
        this.cargarPrimitivosyVoid();
        this.agregarPredefinidas();
    }

    /**
     * muestra todo lo que tiene la clase
     * Variables de instancia, metodos y constructores
     */
    public void mostrarTodaLaClase(String clase) throws Exception {
        //Iterator iter = this.darClase(clase).tablaMetodo.entrySet().iterator();
        Map.Entry e;
        entradaMetodo entryMethod;
        entradaVariable entryVar;

        System.out.println("Clase : " + clase);
        for (Map.Entry entry : this.darClase(clase).tablaVariable.entrySet()) {
            //Esto recorre todas las variables de la clase "clase".
            entryVar = this.darClase(clase).darVariable((String) entry.getKey());
            System.out.println("--------------------------------------------");
            System.out.println("Variables de Instancia : ");
            System.out.println("Nombre Variable : " + entry.getKey());
            System.out.println("Tipo Variable : " + entryVar.toStringTipoV());
            System.out.println("Offset : " + entryVar.darOffsetVarLocal());
            System.out.println("--------------------------------------------");
        }
        for (Map.Entry entry : this.darClase(clase).tablaMetodo.entrySet()) {
            //Esto recorre todos los metodos de la clase "clase".
            entryMethod = this.darClase(clase).darMetodo((String) entry.getKey());
            this.darClase(clase).mostrarMetodo(entryMethod);
        }
        System.out.println("========================================");
    }

    public void mostrarClases() throws Exception {
        Iterator iter = tablaClase.entrySet().iterator();
        Map.Entry e;
        entradaClase entryClass;

        for (Map.Entry entry : tablaClase.entrySet()) {
            //Esta recorre todas las clases.
            this.mostrarTodaLaClase((String) entry.getKey());
        }
        /*
        while (iter.hasNext())
        {
        e = (Map.Entry)iter.next();
        entryClass = tablaClase.get((String)e.getKey());
        System.out.println(entryClass.darNombre());                      
        }*/
    }

    /**
     * Metodo privado cuya funcion es volcar los parametros de un una entrada
     * de metodo a una lista temporal.
     */
    private void volcarTablaaListaTemporal(entradaMetodo em, ArrayList<parametro> temp) {
        String idparametrotmp, tipoptmp;
        Iterator iter = em.tablaParametros.entrySet().iterator();
        Map.Entry e;
        entradaParametro ptmp;
        while (iter.hasNext()) {
            e = (Map.Entry) iter.next();
            ptmp = em.tablaParametros.get((String) e.getKey());
            idparametrotmp = ptmp.darNombre();
            tipoptmp = ptmp.toStringTipoP();
            temp.add(new parametro(idparametrotmp, tipoptmp));
        }

    }

    /**
     * Verifica que los parametros de una entradaMetodo coincida con los parametros
     * temporales almacenado el el arreglo al. deben coincidir tanto en la ubicacion 
     * como la cantidad de parametros, el nombre de los parametros y el tipo de 
     * cada uno de los parametros.
     * Se usa un arreglo temporal lt que representa los parametros temporales 
     * del metodo acorde a su declaracion.
     * @param al representa los parametros definidos para el metodo @link em
     * 
     */
    @SuppressWarnings("empty-statement")
    private boolean listaParametrosOkEnTablaM(entradaMetodo em, ArrayList<parametro> al) throws Exception {
        boolean todoOk = true;
        int indice = 0, posact = 0;
        boolean seguir = true;
        String m;
        String nombreaux;
        ArrayList<parametro> lt = new ArrayList();
        volcarTablaaListaTemporal(em, lt);
        parametro paux;
        String s1, s2;
        if (em.esConstructorDeClase()){
        nombreaux="constructor";}
        else{
        nombreaux="metodo";}
        if (al == null) {
            if (!lt.isEmpty()) {
                seguir = false;
                todoOk = false;
                m = "Hay mas parametros declarados (" + lt.size() + ")\n que definidos (0) en el "+nombreaux+" "+ 
                        em.darNombre()+" de la clase: "+em.darNombreClasePertenece() + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            }
        } else {
            indice = al.size();
            if (lt.size() > al.size()) {
                seguir = false;
                todoOk = false;
                m = "Hay mas parametros declarados (" + lt.size() + ") que definidos (" + al.size() + ")\n en el "+nombreaux+" "+
                em.darNombre()+" de la clase: "+em.darNombreClasePertenece() + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            } else {
                if (lt.size() < al.size()) {
                    seguir = false;
                    m = "Hay mas parametros definidos(" + al.size() + ")\nque declarados (" + lt.size()+
                             ") en el "+nombreaux+" "+ em.darNombre()+" de la clase: "+em.darNombreClasePertenece()+ ". Linea: " + getLineaActual();
                    mensajeError = errorTS.construirErrorTS(m, lineaActual);
                    throw new errorTS(mensajeError);
                }
            }
        }
        while ((seguir) && posact < indice) {
            {
                if (al.get(posact).sonParametrosIguales(lt.get(posact))) {
                    posact = posact + 1;
                } else {
                    s1 = lt.get(posact).getId();
                    s2 = al.get(posact).getId();
                    if (!(s1.compareTo(s2) == 0)) {
                        m = "El "+nombreaux+" "+em.darNombre()+" de la clase: "+em.darNombreClasePertenece() + " tiene declarado el parametro " + lt.get(posact).getId()
                                + " de tipo " + lt.get(posact).getTipo() + "\ny esta definiendo uno de nombre " + al.get(posact).getId() + ". Linea: " + getLineaActual();
                        mensajeError = errorTS.construirErrorTS(m, lineaActual);
                        throw new errorTS(mensajeError);
                    } else {
                        m = "El "+nombreaux+" "+ em.darNombre()+" de la clase: "+em.darNombreClasePertenece() + " tiene declarado el parametro " + lt.get(posact).getId()
                                + " de tipo " + lt.get(posact).getTipo() + "\ny esta definiendo uno de tipo " + al.get(posact).getTipo()+ ". Linea: " + getLineaActual();
                        mensajeError = errorTS.construirErrorTS(m, lineaActual);
                        throw new errorTS(mensajeError);
                    }
                }
            }
        }
        return todoOk;
    }

    /**
     * Este metodo se encarga de verificar que el metodo declarado consuerde con
     * los parametros pasados al metodo. Esto es hay una correspondencia univoca
     * con el nombre de la clase, el nombre del metodo, el tipo de retorno del 
     * metodo y los parametros.
     * @param nc es el nombre de la clase donde esta el metodo que se quiere 
     * verificar.
     * @param nm es el nombre del metodo a verificar.
     * @param ret es el tipo de retorno del metodo que se quiere verificar.
     * @param 
     */
    public boolean concuerdaConDeclaracion(String nc, String nm, String ret, ArrayList<parametro> lp) throws Exception {
        boolean concuerda = false;
        entradaMetodo em;    
        String m;
        if (!this.perteneceTS(nc)) {
            mensajeError = "La clase " + nc + " no fue declarada." + ". Linea: " + getLineaActual();
            throw new errorTS(mensajeError);
        } else {
            em = this.darClase(nc).darMetodo(nm);
            if (em == null) {
                m = "El metodo " + nm + " no fue declarado en la clase " + nc+ ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            } else {
                if (!em.esMismoRetorno(ret)) {
                    m = "Se esperaba un retorno del tipo "
                            + em.darRetornoMetodo().darNombre() + " y se encontro uno del tipo " + ret
                            + "\npara el metodo " + nm + " en la clase " + nc + ". Linea: " + getLineaActual();
                    mensajeError = errorTS.construirErrorTS(m, lineaActual);
                    throw new errorTS(mensajeError);
                } else {
                    if (em.estaMetodoDefinido()) {
                        m="Implementacion de metodo duplicada: "+nm + ". Linea: " + getLineaActual();
                        mensajeError = errorTS.construirErrorTS(m, lineaActual);
                        throw new errorTS(mensajeError);
                    } else {
                        if (listaParametrosOkEnTablaM(em, lp)) {
                            concuerda = true;
                        }
                    }

                }
            }
        }
        return concuerda;
    }

    /**
     * Dada una lista de parametros las agrega a la tabla de parametros del metodo.
     * @param em es la entrada a metodos que se deben agregar los parametros.
     * @param lp lista de parametros
     */
    public void agregarParametros(entradaMetodo em, ArrayList<parametro> lp) throws Exception {
        int indice, cant;
        cant = lp.size();
        indice = 0;
        entradaParametro ep;
        entradaClase ec;
        String nameauxtp, nameauxid;

        while (indice < cant) {
            nameauxtp = lp.get(indice).getTipo();
            nameauxid = lp.get(indice).getId();
            if (this.darClase(nameauxtp) == null) //tiene un parametro de clase no declarada
            {
                this.agregarClaseTS(nameauxtp);
            }
            ep = new entradaParametro(nameauxid);
            ep.setearNombreClaseP(em.darNombreClasePertenece());
            ep.setearNombreMetodoP(em.darNombre());
            ep.setearOffsetParametro(indice);
            ep.setearTipoParametro(this.darClase(nameauxtp));
            em.setLineaError(lineaActual);
            em.insertarParametro(ep);
            indice = indice + 1;
        }
    }

    /**
     * Dado el nombre de una clase, el nombre de un tipo de retorno de metodo,el 
     * nombre de un metodo, y un arreglo de parametros se intenta agregar ese 
     * metodo con todas sus notaciones en la tabla de simbolos.
     * @param nc es el nombre de la clase.
     * @param tr es el tipo de retorno del metodo a agregar.
     * @param nm es el nombre del metodo
     * @param pm es la lista de parametros del metodo.
     */
    public void agregarMetodo(String nc, String tr, String nm, ArrayList<parametro> pm) throws Exception {
        /* si estoy agregando metodos es porque ya se proceso el nombre 
         * de la clase en classDef. Para distinguir cual de los insertar voy 
         * a usar. */
        entradaClase ec;
        entradaMetodo em;
        String label;
        String m;
        if (nm.compareTo(nc) == 0) {
            m= "El metodo " + nm + " tiene el mismo nombre que la clase"+ ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(m, lineaActual);
            throw new errorTS(mensajeError);
        } else {
            ec = this.darClase(nc);
            if (ec == null) {
                m = "No existe la clase " + nc + " para declarar el metodo " + nm + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            } else {
                if (!(this.perteneceTS(tr))) {
                    this.agregarClaseTS(tr);
                }
                if (ec.perteneceTM(nm)) {
                    entradaMetodo eM = ec.darMetodo(nm);
                    if (eM.darNombreClasePertenece().equals(ec.darNombre())) {
                        m = "El metodo " + nm + " ya fue declarado en la clase " + nc + ". Linea: " + getLineaActual();
                        mensajeError = errorTS.construirErrorTS(m, lineaActual);
                        throw new errorTS(mensajeError);
                    } else {
                        em = new entradaMetodo(nc, nm);
                        em.setearRetorno(this.darClase(tr));
                        //lclaseMetodo
                        em.setearCantParametros(pm.size());
                        label = "l" + nc + nm;
                        em.setearEtiqueta(label);
                        agregarParametros(em, pm);
                        ec.insertarMetodoTM(em);
                    }
                } else {

                    em = new entradaMetodo(nc, nm);
                    em.setearRetorno(this.darClase(tr));
                    label = "l" + nc + nm;
                    em.setearCantParametros(pm.size());
                    agregarParametros(em, pm);
                    ec.insertarMetodoTM(em);
                }
            }

        }
    }

    /**
     * Consulta si el atributo/variable de instancia esta definido en la clase
     * @param nameClase indica la clase donde se quiere consultar por la variable.
     * @param nameAtributo indica el nombre del atributo por el que se quiere consutlar
     * @return Verdadero si la variable pertence a la clase, falso en caso contrario.
     */
    public boolean pertenceAtributo(String nameClase, String nameAtributo) {
        return this.darClase(nameClase).perteneceTV(nameAtributo);
    }

    /**
     * Agrega una variable a la clase.
     * @param nombreTipo indica de que tipo es la variable.
     * @param nombreAtributo es el nombre del atributo que quiere definirse en la 
     * clase.
     * @throws Exception reporta un error cuando se intenta agregar una variable
     * con el mismo nombre de una ya definida para esa clase.
     */
    public void agregarAtributoAClase(String nombreTipo, String nombreAtributo) throws Exception {
        String m;
        if (this.darClase(claseActual).perteneceTV(nombreAtributo)) {
            m = "La variable " + nombreAtributo + " ya fue definida en la clase " + claseActual + ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(m, lineaActual);
            throw new errorTS(mensajeError);
        } else {
            entradaVariable v = new entradaVariable(nombreAtributo);
            entradaClase eCtipo = this.darClase(nombreTipo);
            if (eCtipo != null) {
                v.setearTipo(eCtipo);
                this.darClase(claseActual).setLineaError(lineaActual);
                this.darClase(claseActual).insertarVariableTV(v);
            } else {
                m = "El tipo " + nombreTipo +" de la variable: "+nombreAtributo+", no es conocido" + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(m, lineaActual);
                throw new errorTS(mensajeError);
            }


        }
    }

    /**
     * Agrega una variable a la clase con nombre nc.
     * @param nc es el nombre de la clase a la cual se quiere agregar
     * la variable de instancia.
     * @param nombreTipo indica de que tipo es la variable.
     * @param nombreAtributo es el nombre del atributo que quiere definirse en la 
     * clase.
     * @throws Exception reporta un error cuando se intenta agregar una variable
     * con el mismo nombre de una ya definida para esa clase.
     */
    public void agregarAtributoAClase(String nc, String nombreTipo, String nombreAtributo) throws Exception {
        String m;
        if (this.darClase(nc).perteneceTV(nombreAtributo)) {
            m = "La variable " + nombreAtributo + " ya fue definida para la clase " + nc+ ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(m, lineaActual);
            throw new errorTS(mensajeError);
        } else {
            entradaVariable v = new entradaVariable(nombreAtributo);
            v.setearTipo(this.darClase(nombreTipo));
            this.darClase(nc).insertarVariableTV(v);
        }
    }

//////----------------------------------------------------------------------------------------------------------------------------------------------
//METODOS POR IMPLEMENTAR---- INTERFACE DE LA TABLA DE SIMBOLOS
// Permite la comunicacion entre la tabla de simbolos y el modulo Asintactico,
//el cual tambien tendra chequeos semanticos
    //ok
    public void controlarDefinicionDeClase(String nombreClasePadre) throws Exception {
        //debe controlar que la definicion de clases corresponda con especificacion de la clase
        //si no corresponde debe disparar una excepcion.
        String msg;
        entradaClase eClase = darClase(claseActual);
        if (eClase != null) {
            if (!eClase.darPadre().darNombre().equals(nombreClasePadre)) {
                msg = "La definicion de herencia de la clase \""+claseActual+"\" no concuerda con la declaracion \n en la seccion de classDef" + ". Linea: " + getLineaActual();
                mensajeError = errorTS.construirErrorTS(msg, lineaActual);
                throw new errorTS(mensajeError);
            }
        } else {
            msg = "La clase " + claseActual + " no fuen declarada para su uso en la seccion classDef.\n No es una clase conocida" + ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(msg, lineaActual);
            throw new errorTS(mensajeError);
        }

    }

    public String claseActual() {
        //retorna un identificador del la clase en analisis
        return claseActual;
    }

    public void setearClaseActual(String nombreClaseActual) {
        //setea el nombre de la clase actual que estara en analisis
        seConsultoPorConstructor = false;
        claseActual = nombreClaseActual;
    }
    //-->ok

    public boolean claseActualTieneConstructor() {
        // controla que la clase actual tiene o no un constructor 
        String clase = claseActual;
        //boolean tieneConstructor = false;
        boolean tieneConstructor = this.darClase(clase).tieneDefinidoConstructor();
        return tieneConstructor;
    }
    //TODO

    public void crearConstructorPorDefecto() throws Exception {
        // en caso de que el usuario no diseña un constructor para la clase actual, el compilador creara un constructor por defecto
        // para la clase actual
        this.agregarConstructor();
        entradaClase ec = this.darClase(claseActual);
        ec.setConstructordefault(true);

    }

    public void agregarConstructor() throws Exception {
        //se puede pasar por parametro o accder localmente en el metodo a la lista de parametros mediante funciones de la tabla de simbolos.
        //Debe controlar que el nombre de la clase actual sea igual a ConstMethodDef.identificadorTipo.
        //que no exista ya definido un constructor.
        //si no esta definido setea que la clase ya tiene un constructor otorgado por el usuario
        //recorre la lista de parametros y los agrega al constructor.
        //este metodo debe controlar que los tipos utilizados(retorno y de los parametros) esten definidos
        //si no estna definidos se los debe poner en la lista de definicion pendientes.
        entradaMetodo eM = new entradaMetodo(claseActual, claseActual);
        String msg;
        eM.setearEsConstructor();
        eM.estaMetodoDefinido();
        //this.limpiarListaParametrosTemporales();
        agregarParametros(eM, this.listaTemporalParametros);
        entradaClase ec = this.darClase(claseActual);
        eM.setearEsConstructor();
        if (ec.tieneDefinidoConstructor()) {
            msg="Solo se permite declarar un constructor por clase."
                +"\nNombre del constructor duplicado: "+claseActual+ ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(msg, lineaActual);
            throw new errorTS(mensajeError);            
        }
        String label = "LCtor" + claseActual;
        eM.setearEtiqueta(label);
        eM.setearRetorno(ec);
        ec.insertarMetodoTM(eM);
        ec.setearConstructor();
    }

    public void controlarConstructor() throws Exception {
        //controla que la definicion del contructor corresponda con la especificacion del mismo.
        //y que sea el unico constructor creado
        //teniendo la clase actual
        String msg;
        if (seConsultoPorConstructor) {
            msg="Solo se permite definir un constructor por clase\n"
                +"Nombre del constructor duplicado: "+claseActual+ ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(msg, lineaActual);
            throw new errorTS(mensajeError);            
        } else {
            concuerdaConDeclaracion(claseActual, claseActual, claseActual, listaTemporalParametros);
            entradaClase ec = this.darClase(claseActual);
            if (ec.tieneDefinidoConstructor() && !ec.darMetodo(claseActual).esConstructorDeClase()) {
                throw new errorTS("ocurrio un error en la definicion del constructor" + ". Linea: " + getLineaActual());
            }
        }
        this.seConsultoPorConstructor = true;
    }

    /*
    public void agregarMetodo(String nombreTipoRetorno, String nombreMetodo) {
    //TS.agregarMetodo(ConstMethodDef.identificadorOTipo, id.getLexema(), Ts.listaParametros);
    //Debe controlar que el nombre del metodo no tenga colision con el constructor de clase
    //que no exista ya definido un metodo con la misma signature
    //debe setear que a este metodo le falta la implementacion
    //recorre la lista de parametros y los agrega al metodo
    //TS.agregarMetodo(type, nombre, parametros);
    //le de la lista temporal de parametros seteados en la tabla de simbolos
    //este metodo debe controlar que los tipos utilizados(retorno y de los parametros) esten definidos
    //si no estan definidos se los debe poner en la lista de definicion pendientes.
    //se deben limpiar la lista de parametros temporales una vez que fueron agegador
    
    }*/
    /*
    public void agregarParametroListaTemporal(parametro p) {
    //el metodo agregarParametrosAMetodoActual debera controlar los nombres de los parametros que no este repetidos.
    //agrega parametros temporalmete. esta lista debe limpiarse una vez que se termina de analizar el metodo y se agregan
    //los parametros al metodo
    //debe limpiar la lista de parametros
    }
     */
    public void quitarClaseDeListaPendienteEspecificacion() {
        //quita la clase actual de especificaciones pendientes;
        entradaClase ec = this.darClase(claseActual);
        ec.setearDefinida();
    }
    //ok

    public void setearMetodo(String nombreMetodoActual) throws Exception {
        //setea el metodo actual en la tabla de simbolos
        //y setear el offset , el label de donde esta el codigo del metodo;
        entradaClase eC = this.darClase(claseActual);
        //entradaMetodo eM = eC.darMetodo(nombreMetodoActual);
        //puede ser el label tambien
        eC.setearOffsetMetodo(nombreMetodoActual);
        metodoActual = nombreMetodoActual;
    }
    //ok

    public void quitarConstructorEspecificacionPendiente() throws Exception {
        entradaClase ec = this.darClase(claseActual);
        ec.darMetodo(claseActual).setearDefinido();
    }
    
    public boolean esParametro(String nombreVariable) throws Exception{
        entradaClase eC = this.darClase(claseActual);
        entradaMetodo eM = eC.darMetodo(metodoActual);
        entradaParametro p = eM.darParametro(nombreVariable);
        boolean esP = false;
        if(p != null) esP = true;
        return esP;
    }
    
    public boolean esVarInstancia(String nombreVariable) throws Exception{
        entradaClase eC = this.darClase(claseActual);
        entradaVariable eV = eC.darVariable(nombreVariable);
        boolean esP = false;
        if(eV != null)  esP = true;
        return esP;
    }

    public void quitarMetodoEspecificacionPendiente(String nombreMetodo) throws Exception {
        entradaClase ec = this.darClase(claseActual);
        ec.darMetodo(nombreMetodo).setearDefinido();
    }

    public void controlarTipoRetorno(String nombreTipoRetorno) throws Exception {
        //segun clase actual y metodo actual decide si es admisible o no el typo del retorno
        //sion dispara una exception
        entradaClase ec = darClase(claseActual);
        entradaMetodo em = ec.darMetodo(metodoActual);
        entradaClase cRetorno = em.retorno;
        String msg;
        if (!sonTiposAdmisibles(cRetorno.darNombre(), nombreTipoRetorno)) {
            msg = "El tipo de retorno del metodo no es el esperado. Metodo:" + em.darNombre() ;
            throw new Exception(msg);
        }
    }

    private boolean admisible(String tipo1, String tipo2) {
        String nombrePadre = "";
        boolean son = false;
        entradaClase A = this.darClase(tipo1);
        entradaClase B = this.darClase(tipo2);

        while (!A.esTipoPrimitivo() && !B.esTipoPrimitivo()
                && !son && !nombrePadre.equals("Object")) {
            B = this.darClase(A.darPadre().darNombre());
            if (B.darNombre().equals(tipo2)) {
                son = true;
            } else {
                A = B;
                nombrePadre = B.darNombre();
            }
        }
        return son;
    }

    public boolean sonTiposAdmisibles(String tipo1, String tipo2) {
        boolean sonAdmisible = false;
        if (tipo1.equals(tipo2) || admisible(tipo1, tipo2)) {
            sonAdmisible = true;
        }
        //controles de herencia 
        return sonAdmisible;
    }

    public boolean isAssignable() {
        //retorna si la expresion en primary es una expression asignable: this o id.
        return isAssignable;
    }

    public void setIsAssignable(Boolean assignable) {
        // setea un valor booleano  en la variable isAssignable
        isAssignable = assignable;
    }

    public String tipoRetornoMetodo(String nombreClase, String nombreMetodo) throws Exception {
        //este metodo recibe como parametro el nombre de la clase que se esta invocando el metodo.
        //y el nombre del metodo. Debe retornar el tipo de retorno del metodo
        //si retorna:
        //null: el metodo no es un metodo de la clase PrimaryRec.type
        //tipo: es el valor de retorno dle metodo (el cual puede ser void)
        //el parametro de entrada PrimaryRec.type puede ser null por loque este metodo debe controlar 
        //este caso..si es nulo es porque la variable con la que se intenta invocar el metodo no es alcanzable.
        entradaClase ec = this.darClase(nombreClase);
        entradaMetodo em = ec.darMetodo(nombreMetodo);
        String tipo;
        if(em == null){
            tipo = null;
        }
        else{
            tipo = em.darRetornoMetodo().darNombre();
        }
        return tipo;
    }

    public String buscarTipoDeVar(String nombreVariable) throws Exception {
        //El metodo buscarTipo  va a buscar primero si la variable es un parametro y luego si es una variable de instancia.
        //si devuelve null es porque no es una variable que se pueda alcanzar desde esta clase.
        entradaClase eC = this.darClase(claseActual);
        entradaMetodo eM = eC.darMetodo(metodoActual);
        entradaClase tipoParametro;
        String tipo;
        LinkedHashMap<String, entradaParametro> tablaParametros = eM.getTablaParametros();
        entradaParametro eP = tablaParametros.get(nombreVariable);
        if (eP == null) {
            //buscar en varinstancia
            // if(eC.darVariable(nombreVariable))
            eC.setLineaError(lineaActual);
            tipo = eC.darVariable(nombreVariable).darTipo().darNombre();
        } else {
            tipoParametro = eP.getTipoDeParametro();
            tipo = tipoParametro.darNombre();
        }
        return tipo;
    }

    public parametro obtenerParametro(ArrayList<parametro> listaParametros, int posParametro, int cantParamtros) throws Exception {
        String msg;
        parametro t = null;
        /*if(listaParametros.size()<=posParametro){
        t = listaParametros.get(posParametro);
        }else{
        String msg = "Error al intentar acceder a la lista de parametros del metodo. Indice fuera de rango";
        throw new Exception(msg);
        
        }*/
        try {
            t = listaParametros.get(posParametro);
        } catch (Exception e) {
            t = null;
        }
        if (t == null) {
            msg="La cantidad de parametros actuales no se corresponde con la cantidad de paramtros formales" + ". Linea: " + getLineaActual();
            mensajeError = errorTS.construirErrorTS(msg, lineaActual);
            throw new errorTS(mensajeError);            
        }

        return t;
    }

    public ArrayList<parametro> obtenerListaParametros(String nombreClase, String nombreMetodo) throws Exception {
        entradaClase eC = darClase(nombreClase);
        entradaMetodo eM = eC.darMetodo(nombreMetodo);
        LinkedHashMap<String, entradaParametro> tablaParametros = eM.getTablaParametros();
        entradaParametro ep;
        ArrayList<parametro> arrayParam = new ArrayList<parametro>();
        parametro p;
        for (Map.Entry entry : tablaParametros.entrySet()) {
            ep = eM.darParametro((String) entry.getKey());
            p = new parametro(ep.darNombre(), ep.toStringTipoP());
            arrayParam.add(p);
        }
        return arrayParam;
    }

    public ArrayList<parametro> obtenerListaTemporalParametros() {
        return this.listaTemporalParametros;
    }
    //ok

    public String obtenerNombreClasePadre(String nombreClaseActual) {
        entradaClase ec = this.darClase(nombreClaseActual);
        return ec.darPadre().darNombre();
    }

    public void chequearValoresRetorno(String nombreMetodo) {
        // debe chequer si hay valores de retorno del metodo que se hayan retornado
    }

    /**
     * Consulta si el atributo/variable de instancia esta definido en la clase
     * @param nameClase indica la clase donde se quiere consultar por la variable.
     * @param nameAtributo indica el nombre del atributo por el que se quiere consutlar
     * @return Verdadero si la variable pertence a la clase, falso en caso contrario.
     */
    public boolean esVariableInstancia(String nombreVarieable) {
        //TODo de la clase actual
        return pertenceAtributo(claseActual, nombreVarieable);
    }

    public boolean esMetodo(String nombreMetodo) throws Exception {
        //es un metodo de la clase actual (no super)
        //this.nombremetodo()
        entradaClase ec = darClase(claseActual);
        entradaMetodo em = ec.darMetodo(nombreMetodo);
        boolean es = false;
        if (em != null) {
            es = true;
        }
        return es;
    }

    /**
     * Constructor de la Tabla de simbolos.
     */
    public TabladeSimbolos() {
    }
}
